This is an R Markdown Notebook

Lecture 26 - Your project. Deep Learning with H2O. P.2 Build Deep Learning Model for our example

Deep Learning… This lecture is dedicated to the implementation of Deep Learning models into for our Data

Work overview (from previous lecture)

  • re-arranging data as matrix
  • fitting deep learning models
  • testing the models
  • saving models
  • implementation in our ShinyApp…

The idea will be to use one particular feature e.g. Tubing Process, Impedance to play with. We will not yet implement this in Shiny but rather focus to prepare our data, fit and test the model

Re-arranging data as a matrix

Let’s get our time - series data as a dataframe first…

library(tidyverse)
library(plotly)
# ============= READ DATA =================
# Read our small data ... 
DF_Data_Recent <- readRDS("DF_Data_Process_Recent.data") 
DF_Equipm <- read_csv("DF_EquipmData.csv")
# data frame containing Event Names
DF_EvCode <- read_csv("DF_EvCodeDataProject.csv")
# Data manipulation and saving to the DF_TEMP
DF_TEMP <- DF_Data_Recent %>% 
  # join to decode equipment serial number
  inner_join(DF_Equipm, by = "IDEquipment") %>% 
  # join to decode Event Code meaning
  inner_join(DF_EvCode, by = "EventCode") %>% 
  # select only column needed
  select(StartDate, Name, AnalogVal, EventText)

Then I will plot my data for all 4 machines as just to remember how it looks like…

Looking on the chart above I can see that machine #1 seems to be the best. I will take that one as a reference to build my Deep Learning Model. I will be extracting this dataset with a filter() function.

# extracting only one machine
DF_M1 <- DF_TEMP %>% 
  filter(EventText == "Tubing Process, resistance Ohm") %>% 
  filter(Name == "Machine #1") %>% 
  select(StartDate, AnalogVal) %>% 
  arrange(StartDate) 
head(DF_M1)

Transposing the data

Now we need to transpose our data from long to wide structure and to fit it to matrix! We will ‘forget’ the StartDate values and simply parse the values into matrix of dimension say 150 columns and 20 rows… Of course one need to recover some basic R skills for that :) if not use stackoverflow… (How to turn a vector into a matrix in R?)[https://stackoverflow.com/questions/14614946/how-to-turn-a-vector-into-a-matrix-in-r]

I will also limit the output to 50 pieces to have more clear understanding on the process we do.

DF_M1 <- DF_TEMP %>% 
  filter(EventText == "Tubing Process, resistance Ohm") %>% 
  filter(Name == "Machine #1") %>% 
  arrange(StartDate) %>% 
  select(AnalogVal) %>% 
  head(50) %>% 
  t() %>%  # this brings us a matrix
  matrix(nrow = 10, ncol = 5) # transforming that into matrix size 5x10
DF_M1
      [,1] [,2] [,3] [,4] [,5]
 [1,]   50   44   43   44   51
 [2,]   44   51   49   43   44
 [3,]   43   45   43   49   50
 [4,]   49   51   49   43   44
 [5,]   43   43   45   49   50
 [6,]   50   50   51   43   44
 [7,]   44   44   44   49   45
 [8,]   50   50   51   43   51
 [9,]   44   44   45   52   44
[10,]   50   50   51   45   50

Notice how this matrix is populated - it first complet column one than goes to column 2, etc

We want to keep different behaviour -> we need to populate by rows from left to right

DF_M1 <- DF_TEMP %>% 
  filter(EventText == "Tubing Process, resistance Ohm") %>% 
  filter(Name == "Machine #1") %>% 
  arrange(StartDate) %>% 
  select(AnalogVal) %>% 
  head(50) %>% 
  t() %>%  # this brings us a matrix
  matrix(nrow = 10, ncol = 5) %>% # transforming that into matrix size 5x10
  t() # transpose to get desired structure
DF_M1
     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
[1,]   50   44   43   49   43   50   44   50   44    50
[2,]   44   51   45   51   43   50   44   50   44    50
[3,]   43   49   43   49   45   51   44   51   45    51
[4,]   44   43   49   43   49   43   49   43   52    45
[5,]   51   44   50   44   50   44   45   51   44    50

Our data, however is much larger! Let’s use the recipe to do a final transformation

DF_M1 <- DF_TEMP %>% 
  filter(EventText == "Tubing Process, resistance Ohm") %>% 
  filter(Name == "Machine #1") %>% 
  arrange(StartDate) %>% 
  select(AnalogVal) %>% 
  head(3000) %>% 
  t() %>%  # this brings us a matrix
  matrix(nrow = 150, ncol = 20) %>% # transforming that into matrix size 5x10
  t() # transpose to get desired structure

Wonderful, let’s try to see! our new object as an image!!!

Explore the matrix as a surface!

Let’s use plotly 3D graph to explore what we have got!

library(plotly)
plot_ly(z = DF_M1, type = "surface")
Values from Machine 1 used for Training

Values from Machine 1 used for Training

This should be good enough to fit our deep learning model, but before we will do so I will ‘prepare’ my test datasets. Just to avoid copy/paste this code snippet may become a function:

# build function converting to matrix
to_matrix <- function(x, filter_Event, filter_Machine, n_cols) {
  # get intermediate object and dimension
  #filter_Event <- "Tubing Process, resistance Ohm"
  #filter_Machine <- "Machine #4"
  #x <- DF_TEMP
  #n_cols <- 150
  Step1 <- x %>% 
  filter(EventText == filter_Event) %>% 
  filter(Name == filter_Machine) %>% 
  arrange(StartDate) %>% 
  select(AnalogVal)
  # find number of rows of data frame
  nrows <- Step1 %>% nrow()
  # find the number of row in a matrix
  WN <- nrows/n_cols
  # extract the whole number
  if((WN - round(WN)) < 0){WN <- round(WN) - 1} else {WN <- round(WN)}
  # find number of rows to extract data
  n <- n_cols * WN
  # extract relevant matrix
  Step2 <- Step1 %>% 
    head(n) %>% #only use whole number
  t() %>%  # this brings us a matrix
  matrix(nrow = n_cols, ncol = WN) %>% # transforming that into matrix size 20x150
  t() # transpose to get desired structure
  return(Step2)
}
# set for Machine 2
DF_M2 <- to_matrix(DF_TEMP, 
                   filter_Event = "Tubing Process, resistance Ohm",
                   filter_Machine = "Machine #2", 
                   n_cols = 150)

A. For machine 2:

DF_M2 <- to_matrix(DF_TEMP, 
                   filter_Event = "Tubing Process, resistance Ohm",
                   filter_Machine = "Machine #2", 
                   n_cols = 150)

B. For machine 3:

DF_M3 <- to_matrix(DF_TEMP, 
                   filter_Event = "Tubing Process, resistance Ohm",
                   filter_Machine = "Machine #3", 
                   n_cols = 150)

C. For machine 4:

DF_M4 <- to_matrix(DF_TEMP, 
                   filter_Event = "Tubing Process, resistance Ohm",
                   filter_Machine = "Machine #4", 
                   n_cols = 150)

And we can also visualize that to confront:

For Machine 2

plot_ly(z = DF_M2, type = "surface")
Values from Machine 2 used for Test

Values from Machine 2 used for Test

For Machine 3

plot_ly(z = DF_M3, type = "surface")
Values from Machine 3 used for Test

Values from Machine 3 used for Test

For Machine 4

plot_ly(z = DF_M4, type = "surface")
Values from Machine 4 used for Test

Values from Machine 4 used for Test

Fitting the Deep Learning Model

Launch the machine again…

# to load the library
library(h2o)
# to initialize the 'machine'
localH2O = h2o.init()

H2O is not running yet, starting it now...

Note:  In case of errors look at the following log files:
    C:\Users\fxtrams\AppData\Local\Temp\Rtmpu2t9J7/h2o_fxtrams_started_from_r.out
    C:\Users\fxtrams\AppData\Local\Temp\Rtmpu2t9J7/h2o_fxtrams_started_from_r.err

java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)

Starting H2O JVM and connecting: . Connection successful!

R is connected to the H2O cluster: 
    H2O cluster uptime:         2 seconds 86 milliseconds 
    H2O cluster version:        3.14.0.7 
    H2O cluster version age:    23 days  
    H2O cluster name:           H2O_started_from_R_fxtrams_rxj114 
    H2O cluster total nodes:    1 
    H2O cluster total memory:   1.77 GB 
    H2O cluster total cores:    4 
    H2O cluster allowed cores:  4 
    H2O cluster healthy:        TRUE 
    H2O Connection ip:          localhost 
    H2O Connection port:        54321 
    H2O Connection proxy:       NA 
    H2O Internal Security:      FALSE 
    H2O API Extensions:         Algos, AutoML, Core V3, Core V4 
    R Version:                  R version 3.2.5 (2016-04-14) 

Load datasets to H2O

Then we will download the datasets into h2o. Remember H2O is just operated from R but it’s a computer besides!

# Import train data into the H2O cluster
train_M1 <- as.h2o(x = DF_M1, destination_frame = "train_M1")

  |                                                                                                                      
  |                                                                                                                |   0%
  |                                                                                                                      
  |================================================================================================================| 100%
# Also import our test datasets for Machines 2 and 3...
test_M2  <- as.h2o(x = DF_M2, destination_frame = "test_M2")

  |                                                                                                                      
  |                                                                                                                |   0%
  |                                                                                                                      
  |================================================================================================================| 100%
test_M3  <- as.h2o(x = DF_M3, destination_frame = "test_M3")

  |                                                                                                                      
  |                                                                                                                |   0%
  |                                                                                                                      
  |================================================================================================================| 100%
# Make your own research usign DF_M4!

Building Deep Learning model with autoencoder

Now, once we know how our data looks like we can start to do our Anomaly Model

# Train deep autoencoder learning model on "normal" 
# training data, y ignored 
normality_model <- h2o.deeplearning(
 x = names(train_M1), 
 training_frame = train_M1, 
 activation = "Tanh", 
 autoencoder = TRUE, 
 hidden = c(50,20,50), 
 sparse = TRUE,
 l1 = 1e-4, 
 epochs = 100)

  |                                                                                                                      
  |                                                                                                                |   0%
  |                                                                                                                      
  |================================================================================================================| 100%

Calculate MSE from the train dataset

Let’s use this model on our training dataset…

Getting to know what AI see:

# visually see it
test_recon_M1 <- h2o.predict(normality_model, train_M1) %>% as.matrix()

  |                                                                                                                      
  |                                                                                                                |   0%
  |                                                                                                                      
  |================================================================================================================| 100%
plot_ly(z = test_recon_M1, type = "surface")

Detect Anomaly using MSE value

Now we will try to use test datasets from machine 2

# Compute reconstruction error with the Anomaly 
# detection app (MSE between output and input layers)
h2o.anomaly(normality_model, test_M2) %>%  as.data.frame() %>% plot.ts(ylim = c(0, 10), type = "p")

And for machine 3

h2o.anomaly(normality_model, test_M3) %>%  as.data.frame() %>% plot.ts(ylim = c(0, 10), type = "p")

It is now telling us that behaviour of Machine 2 is much different from machine 1. And on Machine 3 we have a clearly peaks that are distinguishable to recover anomaly!

Note: You may try to practice with Machine 4!

Saving Deep Learning Model for the future use

To use our model in our ShinyApp we will save it…

if(!file.exists("www/tmp/normality_model.bin")){
h2o.saveModel(normality_model, "www/tmp/normality_model.bin")
h2o.download_pojo(normality_model, "www/tmp", get_jar = TRUE)
}

And let’s not forget to switch off our cluster!

h2o.shutdown(prompt= FALSE)
[1] TRUE

Conclusion

In this example the Anomaly Detection model was able to output the anomaly in rows 21-23.

It learned on the pattern of many vectors and was able to distinguish the anomaly coming on new dataset

Practical use of this model can be to us function h2o.anomaly. In case the MSE value will be high - the anomaly is detected!

Next step

our next step will be to repeat the procedure but on our machine data.

  • re-arranging data as matrix
  • fitting deep learning models
  • testing the models
  • saving models
  • implementation in our ShinyApp…
LS0tDQp0aXRsZTogIkxlY3R1cmUgMjYgLSBJbnRvIERlZXAgTGVhcm5pbmcgUDIiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IGRlZmF1bHQNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCi0tLQ0KDQpUaGlzIGlzIGFuIFtSIE1hcmtkb3duXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tKSBOb3RlYm9vaw0KDQoNCiMjIyBMZWN0dXJlIDI2IC0gWW91ciBwcm9qZWN0LiBEZWVwIExlYXJuaW5nIHdpdGggSDJPLiBQLjIgQnVpbGQgRGVlcCBMZWFybmluZyBNb2RlbCBmb3Igb3VyIGV4YW1wbGUNCg0KRGVlcCBMZWFybmluZy4uLiBUaGlzIGxlY3R1cmUgaXMgZGVkaWNhdGVkIHRvIHRoZSBpbXBsZW1lbnRhdGlvbiBvZiBEZWVwIExlYXJuaW5nIG1vZGVscyBpbnRvIGZvciBvdXIgRGF0YQ0KDQojIyMjIFdvcmsgb3ZlcnZpZXcgKGZyb20gcHJldmlvdXMgbGVjdHVyZSkNCg0KKiByZS1hcnJhbmdpbmcgZGF0YSBhcyBtYXRyaXgNCiogZml0dGluZyBkZWVwIGxlYXJuaW5nIG1vZGVscw0KKiB0ZXN0aW5nIHRoZSBtb2RlbHMNCiogc2F2aW5nIG1vZGVscw0KKiBpbXBsZW1lbnRhdGlvbiBpbiBvdXIgU2hpbnlBcHAuLi4NCg0KVGhlIGlkZWEgd2lsbCBiZSB0byB1c2Ugb25lIHBhcnRpY3VsYXIgZmVhdHVyZSBlLmcuICoqVHViaW5nIFByb2Nlc3MsIEltcGVkYW5jZSoqIHRvIHBsYXkgd2l0aC4gV2Ugd2lsbCBub3QgeWV0IGltcGxlbWVudCB0aGlzIGluIFNoaW55IGJ1dCByYXRoZXIgZm9jdXMgdG8gcHJlcGFyZSBvdXIgZGF0YSwgZml0IGFuZCB0ZXN0IHRoZSBtb2RlbA0KDQojIyMjIFJlLWFycmFuZ2luZyBkYXRhIGFzIGEgbWF0cml4DQoNCkxldCdzIGdldCBvdXIgdGltZSAtIHNlcmllcyBkYXRhIGFzIGEgZGF0YWZyYW1lIGZpcnN0Li4uDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHBsb3RseSkNCg0KIyA9PT09PT09PT09PT09IFJFQUQgREFUQSA9PT09PT09PT09PT09PT09PQ0KIyBSZWFkIG91ciBzbWFsbCBkYXRhIC4uLiANCkRGX0RhdGFfUmVjZW50IDwtIHJlYWRSRFMoIkRGX0RhdGFfUHJvY2Vzc19SZWNlbnQuZGF0YSIpIA0KDQpERl9FcXVpcG0gPC0gcmVhZF9jc3YoIkRGX0VxdWlwbURhdGEuY3N2IikNCiMgZGF0YSBmcmFtZSBjb250YWluaW5nIEV2ZW50IE5hbWVzDQpERl9FdkNvZGUgPC0gcmVhZF9jc3YoIkRGX0V2Q29kZURhdGFQcm9qZWN0LmNzdiIpDQoNCiMgRGF0YSBtYW5pcHVsYXRpb24gYW5kIHNhdmluZyB0byB0aGUgREZfVEVNUA0KREZfVEVNUCA8LSBERl9EYXRhX1JlY2VudCAlPiUgDQogICMgam9pbiB0byBkZWNvZGUgZXF1aXBtZW50IHNlcmlhbCBudW1iZXINCiAgaW5uZXJfam9pbihERl9FcXVpcG0sIGJ5ID0gIklERXF1aXBtZW50IikgJT4lIA0KICAjIGpvaW4gdG8gZGVjb2RlIEV2ZW50IENvZGUgbWVhbmluZw0KICBpbm5lcl9qb2luKERGX0V2Q29kZSwgYnkgPSAiRXZlbnRDb2RlIikgJT4lIA0KICAjIHNlbGVjdCBvbmx5IGNvbHVtbiBuZWVkZWQNCiAgc2VsZWN0KFN0YXJ0RGF0ZSwgTmFtZSwgQW5hbG9nVmFsLCBFdmVudFRleHQpDQpgYGANCg0KVGhlbiBJIHdpbGwgcGxvdCBteSBkYXRhIGZvciBhbGwgNCBtYWNoaW5lcyBhcyBqdXN0IHRvIHJlbWVtYmVyIGhvdyBpdCBsb29rcyBsaWtlLi4uDQoNCmBgYHtyfQ0KIyBjcmVhdGluZyBodW1hbiByZWFkYWJsZSBkYXRhIGFuZCB2aXN1YWxpemUgdGhlbQ0KREZfVEVNUCAlPiUgDQogIGZpbHRlcihFdmVudFRleHQgPT0gIlR1YmluZyBQcm9jZXNzLCByZXNpc3RhbmNlIE9obSIpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gU3RhcnREYXRlLCB5ID0gQW5hbG9nVmFsLCBjb2wgPSBOYW1lKSkgKyBnZW9tX3BvaW50KCkrZmFjZXRfZ3JpZCh+TmFtZSkNCmBgYA0KDQpMb29raW5nIG9uIHRoZSBjaGFydCBhYm92ZSBJIGNhbiBzZWUgdGhhdCBtYWNoaW5lICMxIHNlZW1zIHRvIGJlIHRoZSBiZXN0LiBJIHdpbGwgdGFrZSB0aGF0IG9uZSBhcyBhIHJlZmVyZW5jZSB0byBidWlsZCBteSBEZWVwIExlYXJuaW5nIE1vZGVsLiBJIHdpbGwgYmUgZXh0cmFjdGluZyB0aGlzIGRhdGFzZXQgd2l0aCBhICoqZmlsdGVyKCkqKiBmdW5jdGlvbi4gDQoNCmBgYHtyfQ0KIyBleHRyYWN0aW5nIG9ubHkgb25lIG1hY2hpbmUNCkRGX00xIDwtIERGX1RFTVAgJT4lIA0KICBmaWx0ZXIoRXZlbnRUZXh0ID09ICJUdWJpbmcgUHJvY2VzcywgcmVzaXN0YW5jZSBPaG0iKSAlPiUgDQogIGZpbHRlcihOYW1lID09ICJNYWNoaW5lICMxIikgJT4lIA0KICBzZWxlY3QoU3RhcnREYXRlLCBBbmFsb2dWYWwpICU+JSANCiAgYXJyYW5nZShTdGFydERhdGUpIA0KaGVhZChERl9NMSkNCmBgYA0KDQojIyMjIFRyYW5zcG9zaW5nIHRoZSBkYXRhDQoNCk5vdyB3ZSBuZWVkIHRvICoqdHJhbnNwb3NlKiogb3VyIGRhdGEgZnJvbSBsb25nIHRvIHdpZGUgc3RydWN0dXJlIGFuZCB0byBmaXQgaXQgdG8gYG1hdHJpeGAhIFdlIHdpbGwgJ2ZvcmdldCcgdGhlIFN0YXJ0RGF0ZSB2YWx1ZXMgYW5kIHNpbXBseSBwYXJzZSB0aGUgdmFsdWVzIGludG8gbWF0cml4IG9mIGRpbWVuc2lvbiBzYXkgMTUwIGNvbHVtbnMgYW5kIDIwIHJvd3MuLi4gT2YgY291cnNlIG9uZSBuZWVkIHRvIHJlY292ZXIgc29tZSBiYXNpYyBSIHNraWxscyBmb3IgdGhhdCA6KSBpZiBub3QgdXNlIHN0YWNrb3ZlcmZsb3cuLi4gKEhvdyB0byB0dXJuIGEgdmVjdG9yIGludG8gYSBtYXRyaXggaW4gUj8pW2h0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzE0NjE0OTQ2L2hvdy10by10dXJuLWEtdmVjdG9yLWludG8tYS1tYXRyaXgtaW4tcl0NCg0KSSB3aWxsIGFsc28gbGltaXQgdGhlIG91dHB1dCB0byA1MCBwaWVjZXMgdG8gaGF2ZSBtb3JlIGNsZWFyIHVuZGVyc3RhbmRpbmcgb24gdGhlIHByb2Nlc3Mgd2UgZG8uDQoNCmBgYHtyfQ0KREZfTTEgPC0gREZfVEVNUCAlPiUgDQogIGZpbHRlcihFdmVudFRleHQgPT0gIlR1YmluZyBQcm9jZXNzLCByZXNpc3RhbmNlIE9obSIpICU+JSANCiAgZmlsdGVyKE5hbWUgPT0gIk1hY2hpbmUgIzEiKSAlPiUgDQogIGFycmFuZ2UoU3RhcnREYXRlKSAlPiUgDQogIHNlbGVjdChBbmFsb2dWYWwpICU+JSANCiAgaGVhZCg1MCkgJT4lIA0KICB0KCkgJT4lICAjIHRoaXMgYnJpbmdzIHVzIGEgbWF0cml4DQogIG1hdHJpeChucm93ID0gMTAsIG5jb2wgPSA1KSAjIHRyYW5zZm9ybWluZyB0aGF0IGludG8gbWF0cml4IHNpemUgNXgxMA0KREZfTTENCmBgYA0KDQpOb3RpY2UgaG93IHRoaXMgbWF0cml4IGlzIHBvcHVsYXRlZCAtIGl0IGZpcnN0IGNvbXBsZXQgY29sdW1uIG9uZSB0aGFuIGdvZXMgdG8gY29sdW1uIDIsIGV0Yw0KDQpXZSB3YW50IHRvIGtlZXAgZGlmZmVyZW50IGJlaGF2aW91ciAtPiB3ZSBuZWVkIHRvIHBvcHVsYXRlIGJ5IHJvd3MgZnJvbSBsZWZ0IHRvIHJpZ2h0DQoNCmBgYHtyfQ0KREZfTTEgPC0gREZfVEVNUCAlPiUgDQogIGZpbHRlcihFdmVudFRleHQgPT0gIlR1YmluZyBQcm9jZXNzLCByZXNpc3RhbmNlIE9obSIpICU+JSANCiAgZmlsdGVyKE5hbWUgPT0gIk1hY2hpbmUgIzEiKSAlPiUgDQogIGFycmFuZ2UoU3RhcnREYXRlKSAlPiUgDQogIHNlbGVjdChBbmFsb2dWYWwpICU+JSANCiAgaGVhZCg1MCkgJT4lIA0KICB0KCkgJT4lICAjIHRoaXMgYnJpbmdzIHVzIGEgbWF0cml4DQogIG1hdHJpeChucm93ID0gMTAsIG5jb2wgPSA1KSAlPiUgIyB0cmFuc2Zvcm1pbmcgdGhhdCBpbnRvIG1hdHJpeCBzaXplIDV4MTANCiAgdCgpICMgdHJhbnNwb3NlIHRvIGdldCBkZXNpcmVkIHN0cnVjdHVyZQ0KREZfTTENCmBgYA0KDQpPdXIgZGF0YSwgaG93ZXZlciBpcyBtdWNoIGxhcmdlciEgTGV0J3MgdXNlIHRoZSByZWNpcGUgdG8gZG8gYSBmaW5hbCB0cmFuc2Zvcm1hdGlvbg0KDQpgYGB7cn0NCkRGX00xIDwtIERGX1RFTVAgJT4lIA0KICBmaWx0ZXIoRXZlbnRUZXh0ID09ICJUdWJpbmcgUHJvY2VzcywgcmVzaXN0YW5jZSBPaG0iKSAlPiUgDQogIGZpbHRlcihOYW1lID09ICJNYWNoaW5lICMxIikgJT4lIA0KICBhcnJhbmdlKFN0YXJ0RGF0ZSkgJT4lIA0KICBzZWxlY3QoQW5hbG9nVmFsKSAlPiUgDQogIGhlYWQoMzAwMCkgJT4lIA0KICB0KCkgJT4lICAjIHRoaXMgYnJpbmdzIHVzIGEgbWF0cml4DQogIG1hdHJpeChucm93ID0gMTUwLCBuY29sID0gMjApICU+JSAjIHRyYW5zZm9ybWluZyB0aGF0IGludG8gbWF0cml4IHNpemUgMjB4MTUwDQogIHQoKSAjIHRyYW5zcG9zZSB0byBnZXQgZGVzaXJlZCBzdHJ1Y3R1cmUNCg0KYGBgDQoNCldvbmRlcmZ1bCwgbGV0J3MgdHJ5IHRvIHNlZSEgb3VyIG5ldyBvYmplY3QgYXMgYW4gaW1hZ2UhISENCg0KIyMjIyBFeHBsb3JlIHRoZSBtYXRyaXggYXMgYSBzdXJmYWNlIQ0KDQpMZXQncyB1c2UgYHBsb3RseWAgM0QgZ3JhcGggdG8gZXhwbG9yZSB3aGF0IHdlIGhhdmUgZ290IQ0KDQpgYGB7ciwgZXZhbD1GQUxTRSwgaW5jbHVkZT1UUlVFfQ0KbGlicmFyeShwbG90bHkpDQpwbG90X2x5KHogPSBERl9NMSwgdHlwZSA9ICJzdXJmYWNlIikNCg0KYGBgDQoNCiFbVmFsdWVzIGZyb20gTWFjaGluZSAxIHVzZWQgZm9yIFRyYWluaW5nXVtpZDFdDQoNCg0KDQpUaGlzIHNob3VsZCBiZSBnb29kIGVub3VnaCB0byBmaXQgb3VyIGRlZXAgbGVhcm5pbmcgbW9kZWwsIGJ1dCBiZWZvcmUgd2Ugd2lsbCBkbyBzbyBJIHdpbGwgJ3ByZXBhcmUnIG15IHRlc3QgZGF0YXNldHMuIEp1c3QgdG8gYXZvaWQgY29weS9wYXN0ZSB0aGlzIGNvZGUgc25pcHBldCBtYXkgYmVjb21lIGEgZnVuY3Rpb246DQoNCmBgYHtyfQ0KIyBidWlsZCBmdW5jdGlvbiBjb252ZXJ0aW5nIHRvIG1hdHJpeA0KdG9fbWF0cml4IDwtIGZ1bmN0aW9uKHgsIGZpbHRlcl9FdmVudCwgZmlsdGVyX01hY2hpbmUsIG5fY29scykgew0KICAjIGdldCBpbnRlcm1lZGlhdGUgb2JqZWN0IGFuZCBkaW1lbnNpb24NCiAgI2ZpbHRlcl9FdmVudCA8LSAiVHViaW5nIFByb2Nlc3MsIHJlc2lzdGFuY2UgT2htIg0KICAjZmlsdGVyX01hY2hpbmUgPC0gIk1hY2hpbmUgIzQiDQogICN4IDwtIERGX1RFTVANCiAgI25fY29scyA8LSAxNTANCiAgU3RlcDEgPC0geCAlPiUgDQogIGZpbHRlcihFdmVudFRleHQgPT0gZmlsdGVyX0V2ZW50KSAlPiUgDQogIGZpbHRlcihOYW1lID09IGZpbHRlcl9NYWNoaW5lKSAlPiUgDQogIGFycmFuZ2UoU3RhcnREYXRlKSAlPiUgDQogIHNlbGVjdChBbmFsb2dWYWwpDQogICMgZmluZCBudW1iZXIgb2Ygcm93cyBvZiBkYXRhIGZyYW1lDQogIG5yb3dzIDwtIFN0ZXAxICU+JSBucm93KCkNCiAgIyBmaW5kIHRoZSBudW1iZXIgb2Ygcm93IGluIGEgbWF0cml4DQogIFdOIDwtIG5yb3dzL25fY29scw0KICAjIGV4dHJhY3QgdGhlIHdob2xlIG51bWJlcg0KICBpZigoV04gLSByb3VuZChXTikpIDwgMCl7V04gPC0gcm91bmQoV04pIC0gMX0gZWxzZSB7V04gPC0gcm91bmQoV04pfQ0KICAjIGZpbmQgbnVtYmVyIG9mIHJvd3MgdG8gZXh0cmFjdCBkYXRhDQogIG4gPC0gbl9jb2xzICogV04NCiAgIyBleHRyYWN0IHJlbGV2YW50IG1hdHJpeA0KICBTdGVwMiA8LSBTdGVwMSAlPiUgDQogICAgaGVhZChuKSAlPiUgI29ubHkgdXNlIHdob2xlIG51bWJlcg0KICB0KCkgJT4lICAjIHRoaXMgYnJpbmdzIHVzIGEgbWF0cml4DQogIG1hdHJpeChucm93ID0gbl9jb2xzLCBuY29sID0gV04pICU+JSAjIHRyYW5zZm9ybWluZyB0aGF0IGludG8gbWF0cml4IHNpemUgMjB4MTUwDQogIHQoKSAjIHRyYW5zcG9zZSB0byBnZXQgZGVzaXJlZCBzdHJ1Y3R1cmUNCiAgcmV0dXJuKFN0ZXAyKQ0KfQ0KYGBgDQoNCg0KDQpgYGB7cn0NCiMgc2V0IGZvciBNYWNoaW5lIDINCkRGX00yIDwtIHRvX21hdHJpeChERl9URU1QLCANCiAgICAgICAgICAgICAgICAgICBmaWx0ZXJfRXZlbnQgPSAiVHViaW5nIFByb2Nlc3MsIHJlc2lzdGFuY2UgT2htIiwNCiAgICAgICAgICAgICAgICAgICBmaWx0ZXJfTWFjaGluZSA9ICJNYWNoaW5lICMyIiwgDQogICAgICAgICAgICAgICAgICAgbl9jb2xzID0gMTUwKQ0KYGBgDQoNCg0KDQpBLiBGb3IgbWFjaGluZSAyOg0KDQpgYGB7cn0NCkRGX00yIDwtIHRvX21hdHJpeChERl9URU1QLCANCiAgICAgICAgICAgICAgICAgICBmaWx0ZXJfRXZlbnQgPSAiVHViaW5nIFByb2Nlc3MsIHJlc2lzdGFuY2UgT2htIiwNCiAgICAgICAgICAgICAgICAgICBmaWx0ZXJfTWFjaGluZSA9ICJNYWNoaW5lICMyIiwgDQogICAgICAgICAgICAgICAgICAgbl9jb2xzID0gMTUwKQ0KYGBgDQoNCkIuIEZvciBtYWNoaW5lIDM6DQoNCmBgYHtyfQ0KREZfTTMgPC0gdG9fbWF0cml4KERGX1RFTVAsIA0KICAgICAgICAgICAgICAgICAgIGZpbHRlcl9FdmVudCA9ICJUdWJpbmcgUHJvY2VzcywgcmVzaXN0YW5jZSBPaG0iLA0KICAgICAgICAgICAgICAgICAgIGZpbHRlcl9NYWNoaW5lID0gIk1hY2hpbmUgIzMiLCANCiAgICAgICAgICAgICAgICAgICBuX2NvbHMgPSAxNTApDQpgYGANCg0KQy4gRm9yIG1hY2hpbmUgNDoNCg0KYGBge3J9DQpERl9NNCA8LSB0b19tYXRyaXgoREZfVEVNUCwgDQogICAgICAgICAgICAgICAgICAgZmlsdGVyX0V2ZW50ID0gIlR1YmluZyBQcm9jZXNzLCByZXNpc3RhbmNlIE9obSIsDQogICAgICAgICAgICAgICAgICAgZmlsdGVyX01hY2hpbmUgPSAiTWFjaGluZSAjNCIsIA0KICAgICAgICAgICAgICAgICAgIG5fY29scyA9IDE1MCkNCmBgYA0KDQpBbmQgd2UgY2FuIGFsc28gdmlzdWFsaXplIHRoYXQgdG8gY29uZnJvbnQ6DQoNCkZvciBNYWNoaW5lIDINCg0KYGBge3IsIGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0NCnBsb3RfbHkoeiA9IERGX00yLCB0eXBlID0gInN1cmZhY2UiKQ0KYGBgDQoNCiFbVmFsdWVzIGZyb20gTWFjaGluZSAyIHVzZWQgZm9yIFRlc3RdW2lkMl0NCg0KRm9yIE1hY2hpbmUgMw0KDQpgYGB7ciwgZXZhbD1GQUxTRSwgaW5jbHVkZT1UUlVFfQ0KcGxvdF9seSh6ID0gREZfTTMsIHR5cGUgPSAic3VyZmFjZSIpDQpgYGANCg0KIVtWYWx1ZXMgZnJvbSBNYWNoaW5lIDMgdXNlZCBmb3IgVGVzdF1baWQzXQ0KDQpGb3IgTWFjaGluZSA0DQoNCmBgYHtyLCBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9DQpwbG90X2x5KHogPSBERl9NNCwgdHlwZSA9ICJzdXJmYWNlIikNCmBgYA0KDQohW1ZhbHVlcyBmcm9tIE1hY2hpbmUgNCB1c2VkIGZvciBUZXN0XVtpZDRdDQoNCiMjIyMgRml0dGluZyB0aGUgRGVlcCBMZWFybmluZyBNb2RlbA0KDQpMYXVuY2ggdGhlIG1hY2hpbmUgYWdhaW4uLi4NCg0KYGBge3IsIGV2YWw9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQ0KIyB0byBsb2FkIHRoZSBsaWJyYXJ5DQpsaWJyYXJ5KGgybykNCg0KIyB0byBpbml0aWFsaXplIHRoZSAnbWFjaGluZScNCmxvY2FsSDJPID0gaDJvLmluaXQoKQ0KYGBgDQoNCiMjIyMgTG9hZCBkYXRhc2V0cyB0byBIMk8NCg0KVGhlbiB3ZSB3aWxsIGRvd25sb2FkIHRoZSBkYXRhc2V0cyBpbnRvIGgyby4gUmVtZW1iZXIgSDJPIGlzIGp1c3Qgb3BlcmF0ZWQgZnJvbSBSIGJ1dCBpdCdzIGEgY29tcHV0ZXIgYmVzaWRlcyENCg0KDQpgYGB7ciwgZXZhbD1UUlVFLCBpbmNsdWRlPVRSVUV9DQojIEltcG9ydCB0cmFpbiBkYXRhIGludG8gdGhlIEgyTyBjbHVzdGVyDQp0cmFpbl9NMSA8LSBhcy5oMm8oeCA9IERGX00xLCBkZXN0aW5hdGlvbl9mcmFtZSA9ICJ0cmFpbl9NMSIpDQoNCiMgQWxzbyBpbXBvcnQgb3VyIHRlc3QgZGF0YXNldHMgZm9yIE1hY2hpbmVzIDIgYW5kIDMuLi4NCnRlc3RfTTIgIDwtIGFzLmgybyh4ID0gREZfTTIsIGRlc3RpbmF0aW9uX2ZyYW1lID0gInRlc3RfTTIiKQ0KdGVzdF9NMyAgPC0gYXMuaDJvKHggPSBERl9NMywgZGVzdGluYXRpb25fZnJhbWUgPSAidGVzdF9NMyIpDQoNCiMgTWFrZSB5b3VyIG93biByZXNlYXJjaCB1c2lnbiBERl9NNCENCg0KYGBgDQoNCg0KIyMjIyBCdWlsZGluZyBEZWVwIExlYXJuaW5nIG1vZGVsIHdpdGggYXV0b2VuY29kZXINCg0KTm93LCBvbmNlIHdlIGtub3cgaG93IG91ciBkYXRhIGxvb2tzIGxpa2Ugd2UgY2FuIHN0YXJ0IHRvIGRvIG91ciBBbm9tYWx5IE1vZGVsDQoNCmBgYHtyLCBldmFsPVRSVUUsIGluY2x1ZGU9VFJVRX0NCg0KIyBUcmFpbiBkZWVwIGF1dG9lbmNvZGVyIGxlYXJuaW5nIG1vZGVsIG9uICJub3JtYWwiIA0KIyB0cmFpbmluZyBkYXRhLCB5IGlnbm9yZWQgDQpub3JtYWxpdHlfbW9kZWwgPC0gaDJvLmRlZXBsZWFybmluZygNCiB4ID0gbmFtZXModHJhaW5fTTEpLCANCiB0cmFpbmluZ19mcmFtZSA9IHRyYWluX00xLCANCiBhY3RpdmF0aW9uID0gIlRhbmgiLCANCiBhdXRvZW5jb2RlciA9IFRSVUUsIA0KIGhpZGRlbiA9IGMoNTAsMjAsNTApLCANCiBzcGFyc2UgPSBUUlVFLA0KIGwxID0gMWUtNCwgDQogZXBvY2hzID0gMTAwKQ0KYGBgDQoNCiMjIyMgQ2FsY3VsYXRlIE1TRSBmcm9tIHRoZSB0cmFpbiBkYXRhc2V0DQoNCkxldCdzIHVzZSB0aGlzIG1vZGVsIG9uIG91ciB0cmFpbmluZyBkYXRhc2V0Li4uDQoNCmBgYHtyLCBldmFsPVRSVUUsIGluY2x1ZGU9VFJVRX0NCiMgY29tcHV0ZXIgZXJyb3Igb2YgdGhlIG1vZGVsDQpoMm8uYW5vbWFseShub3JtYWxpdHlfbW9kZWwsIHRyYWluX00xKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBwbG90LnRzKHlsaW0gPSBjKDAsIDIpLCB0eXBlID0gInAiKQ0KYGBgDQoNCiMjIyMgR2V0dGluZyB0byBrbm93IHdoYXQgQUkgc2VlOg0KDQpgYGB7ciwgZXZhbD1UUlVFLCBpbmNsdWRlPVRSVUV9DQojIHZpc3VhbGx5IHNlZSBpdA0KdGVzdF9yZWNvbl9NMSA8LSBoMm8ucHJlZGljdChub3JtYWxpdHlfbW9kZWwsIHRyYWluX00xKSAlPiUgYXMubWF0cml4KCkNCmBgYA0KDQpgYGB7ciwgZXZhbD1GQUxTRSwgaW5jbHVkZT1UUlVFfQ0KcGxvdF9seSh6ID0gdGVzdF9yZWNvbl9NMSwgdHlwZSA9ICJzdXJmYWNlIikNCmBgYA0KDQoNCiMjIyMgRGV0ZWN0IEFub21hbHkgdXNpbmcgTVNFIHZhbHVlDQoNCk5vdyB3ZSB3aWxsIHRyeSB0byB1c2UgdGVzdCBkYXRhc2V0cyBmcm9tICoqbWFjaGluZSAyKioNCg0KYGBge3IsIGV2YWw9VFJVRSwgaW5jbHVkZT1UUlVFfQ0KIyBDb21wdXRlIHJlY29uc3RydWN0aW9uIGVycm9yIHdpdGggdGhlIEFub21hbHkgDQojIGRldGVjdGlvbiBhcHAgKE1TRSBiZXR3ZWVuIG91dHB1dCBhbmQgaW5wdXQgbGF5ZXJzKQ0KaDJvLmFub21hbHkobm9ybWFsaXR5X21vZGVsLCB0ZXN0X00yKSAlPiUgIGFzLmRhdGEuZnJhbWUoKSAlPiUgcGxvdC50cyh5bGltID0gYygwLCAxMCksIHR5cGUgPSAicCIpDQpgYGANCg0KQW5kIGZvciAqKm1hY2hpbmUgMyoqDQoNCmBgYHtyLCBldmFsPVRSVUUsIGluY2x1ZGU9VFJVRX0NCmgyby5hbm9tYWx5KG5vcm1hbGl0eV9tb2RlbCwgdGVzdF9NMykgJT4lICBhcy5kYXRhLmZyYW1lKCkgJT4lIHBsb3QudHMoeWxpbSA9IGMoMCwgMTApLCB0eXBlID0gInAiKQ0KYGBgDQoNCkl0IGlzIG5vdyB0ZWxsaW5nIHVzIHRoYXQgYmVoYXZpb3VyIG9mIE1hY2hpbmUgMiBpcyBtdWNoIGRpZmZlcmVudCBmcm9tIG1hY2hpbmUgMS4gQW5kIG9uIE1hY2hpbmUgMyB3ZSBoYXZlIGEgY2xlYXJseSBwZWFrcyB0aGF0IGFyZSBkaXN0aW5ndWlzaGFibGUgdG8gcmVjb3ZlciBhbm9tYWx5IQ0KDQoqKk5vdGU6KiogWW91IG1heSB0cnkgdG8gcHJhY3RpY2Ugd2l0aCBNYWNoaW5lIDQhDQoNCg0KIyMjIyBTYXZpbmcgRGVlcCBMZWFybmluZyBNb2RlbCBmb3IgdGhlIGZ1dHVyZSB1c2UNCg0KVG8gdXNlIG91ciBtb2RlbCBpbiBvdXIgU2hpbnlBcHAgd2Ugd2lsbCBzYXZlIGl0Li4uDQoNCmBgYHtyLCBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9DQoNCmlmKCFmaWxlLmV4aXN0cygid3d3L3RtcC9ub3JtYWxpdHlfbW9kZWwuYmluIikpew0KaDJvLnNhdmVNb2RlbChub3JtYWxpdHlfbW9kZWwsICJ3d3cvdG1wL25vcm1hbGl0eV9tb2RlbC5iaW4iKQ0KaDJvLmRvd25sb2FkX3Bvam8obm9ybWFsaXR5X21vZGVsLCAid3d3L3RtcCIsIGdldF9qYXIgPSBUUlVFKQ0KfQ0KYGBgDQoNCkFuZCBsZXQncyBub3QgZm9yZ2V0IHRvIHN3aXRjaCBvZmYgb3VyIGNsdXN0ZXIhDQpgYGB7ciwgZXZhbD1UUlVFLCBpbmNsdWRlPVRSVUV9DQpoMm8uc2h1dGRvd24ocHJvbXB0PSBGQUxTRSkNCg0KYGBgDQoNCiMjIyMgQ29uY2x1c2lvbg0KDQpJbiB0aGlzIGV4YW1wbGUgdGhlIEFub21hbHkgRGV0ZWN0aW9uIG1vZGVsIHdhcyBhYmxlIHRvIG91dHB1dCB0aGUgYW5vbWFseSBpbiByb3dzIDIxLTIzLiANCg0KSXQgbGVhcm5lZCBvbiB0aGUgcGF0dGVybiBvZiBtYW55IHZlY3RvcnMgYW5kIHdhcyBhYmxlIHRvIGRpc3Rpbmd1aXNoIHRoZSBhbm9tYWx5IGNvbWluZyBvbiBuZXcgZGF0YXNldA0KDQpQcmFjdGljYWwgdXNlIG9mIHRoaXMgbW9kZWwgY2FuIGJlIHRvIHVzIGZ1bmN0aW9uICoqaDJvLmFub21hbHkqKi4gSW4gY2FzZSB0aGUgTVNFIHZhbHVlIHdpbGwgYmUgaGlnaCAtIHRoZSBhbm9tYWx5IGlzIGRldGVjdGVkIQ0KDQojIyMjIE5leHQgc3RlcA0KDQpvdXIgbmV4dCBzdGVwIHdpbGwgYmUgdG8gcmVwZWF0IHRoZSBwcm9jZWR1cmUgYnV0IG9uIG91ciBtYWNoaW5lIGRhdGEuIA0KDQoqIHJlLWFycmFuZ2luZyBkYXRhIGFzIG1hdHJpeA0KKiBmaXR0aW5nIGRlZXAgbGVhcm5pbmcgbW9kZWxzDQoqIHRlc3RpbmcgdGhlIG1vZGVscw0KKiBzYXZpbmcgbW9kZWxzDQoqIGltcGxlbWVudGF0aW9uIGluIG91ciBTaGlueUFwcC4uLg0KDQojIyMjIHVzZWQgcmVmZXJlbmNlcw0KDQpleGFtcGxlIGZyb206IChodHRwczovL2R6b25lLmNvbS9hcnRpY2xlcy9hbm9tYWx5LWRldGVjdGlvbi13aXRoLWRlZXAtbGVhcm5pbmctaW4tci13aXRoLWgybylbaHR0cHM6Ly9kem9uZS5jb20vYXJ0aWNsZXMvYW5vbWFseS1kZXRlY3Rpb24td2l0aC1kZWVwLWxlYXJuaW5nLWluLXItd2l0aC1oMm9dDQoNCk1vcmUgcmVhZGluZzogKGh0dHBzOi8vZHpvbmUuY29tL2FydGljbGVzL3RoZS1iYXNpY3Mtb2YtZGVlcC1sZWFybmluZy1ob3ctdG8tYXBwbHktaXQtdG8tcHJlP2Zyb21yZWw9dHJ1ZSlbaHR0cHM6Ly9kem9uZS5jb20vYXJ0aWNsZXMvdGhlLWJhc2ljcy1vZi1kZWVwLWxlYXJuaW5nLWhvdy10by1hcHBseS1pdC10by1wcmU/ZnJvbXJlbD10cnVlXQ0KDQpBbmQ6IChodHRwczovL3NoaXJpbmcuZ2l0aHViLmlvL21hY2hpbmVfbGVhcm5pbmcvMjAxNy8wNS8wMS9mcmF1ZClbaHR0cHM6Ly9zaGlyaW5nLmdpdGh1Yi5pby9tYWNoaW5lX2xlYXJuaW5nLzIwMTcvMDUvMDEvZnJhdWRdDQoNCnBhcGVyOiAoaHR0cHM6Ly9hcnhpdi5vcmcvYWJzLzE3MDEuMDE4ODcpW2h0dHBzOi8vYXJ4aXYub3JnL2Ficy8xNzAxLjAxODg3XQ0KSW4gdGhpcyBsZWN0dXJlIHdlIHdvdWxkIGV4cGxvcmUgdGhlICd0ZWNobm9sb2d5JyBvbiB0aGUgc2FtcGxlIGFuZCB0cnkgdG8gZG8gdGhpcyBpbiAxMCBtaW4gbGVjdHVyZSENCg0KDQoNCltpZDFdOiBwbG90cy9NMV90cmFpbi5wbmcgIkRhdGEgdXNlZCB0byBUcmFpbiB0aGUgbW9kZWwiDQpbaWQyXTogcGxvdHMvTTJfdGVzdC5wbmcgIkRhdGEgdXNlZCB0byBUZXN0IHRoZSBtb2RlbCAtIE1hY2hpbmUgMiINCltpZDNdOiBwbG90cy9NM190ZXN0LnBuZyAiRGF0YSB1c2VkIHRvIFRlc3QgdGhlIG1vZGVsIC0gTWFjaGluZSAzIg0KW2lkNF06IHBsb3RzL000X3Rlc3QucG5nICJEYXRhIHVzZWQgdG8gVGVzdCB0aGUgbW9kZWwgLSBNYWNoaW5lIDQiDQoNCg==